package com.gitlab.techschool.pcbook.service;

import com.gitlab.techschool.pcbook.pb.Filter;
import com.gitlab.techschool.pcbook.pb.Laptop;
import com.gitlab.techschool.pcbook.pb.Memory;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

public class InMemoryLaptopStore implements LaptopStore {

    private final ConcurrentMap<String, Laptop> data;

    public InMemoryLaptopStore() {
        data = new ConcurrentHashMap<>(0);
    }

    @Override
    public void save(Laptop laptop) throws Exception {
        if (data.containsKey(laptop.getId())) {
            throw new AlreadyExistsException("laptop ID already exists");
        }

        // 深拷贝
        Laptop other = laptop.toBuilder().build();
        data.put(other.getId(), other);
    }

    @Override
    public Laptop find(String id) {
        if (!data.containsKey(id)) {
            return null;
        }

        // 深拷贝
        return data.get(id).toBuilder().build();
    }

    @Override
    public void search(Filter filter, LaptopStream laptopStream) {
        for (Map.Entry<String, Laptop> entry : data.entrySet()) {
            Laptop laptop = entry.getValue();
            if (isQualified(filter, laptop)) {
                // 若条件匹配，则将laptop返回（深拷贝）
                laptopStream.send(laptop.toBuilder().build());
            }
        }
    }

    /**
     * 判断laptop是否满足过滤条件
     */
    private boolean isQualified(Filter filter, Laptop laptop) {
        if (laptop.getPriceUsd() > filter.getMaxPriceUsd()) {
            return false;
        }

        if (laptop.getCpu().getNumberCores() < filter.getMinCpuCores()) {
            return false;
        }

        if (laptop.getCpu().getMaxGhz() < filter.getMinCpuGhz()) {
            return false;
        }

        if (toBit(laptop.getRam()) < toBit(filter.getMinRam())) {
            return false;
        }

        return true;
    }

    /**
     * 将Memory对象转换为bit单位的value
     */
    private long toBit(Memory memory) {
        long value = memory.getValue();
        switch (memory.getUnit()) {
            case BIT:
                return value;
            case BYTE:
                // 1 BYTE = 8 BIT = 2^3 BIT
                return value << 3;
            case KILOBYTE:
                // 1 KILOBYTE = 2^10 BYTE = 2^13 BIT
                return value << 13;
            case MEGABYTE:
                return value << 23;
            case GIGABYTE:
                return value << 33;
            case TERABYTE:
                return value << 43;
            default:
                return 0;
        }
    }
}
